﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.IO;

using TJDevHouse.BackupUtils.DirectoryUtils;

namespace TJDevHouse.BackupUtils.CopyModifiedDirs
{
	/// <summary>
	/// Contains the logic and state for copying a single directory
	/// from source to target.
	/// </summary>
	public class DirectoryCopyOperation
	{

		#region Public properties
		/// <summary>
		/// The root directory to be used when generating the target directory name.
		/// </summary>
		public DirectoryInfo SourceRootDirectory { get; private set; }

		/// <summary>
		/// The directory to copy
		/// </summary>
		public DirectoryInfo SourceDirectory { get; private set; }

		/// <summary>
		/// The root directory to copy the target into.
		/// </summary>
		public DirectoryInfo TargetRootDirectory { get; private set; }

		/// <summary>
		/// The final directory the source will be copied to
		/// </summary>
		public DirectoryInfo TargetDirectory
		{
			get
			{
				return new DirectoryInfo(CalculateTargetPath());
			}
		}

		/// <summary>
		/// The directory to copy the source into before renaming to the target directory.
		/// (minimises the time that there is an incomplete copy in the target)
		/// </summary>
		public DirectoryInfo TempTargetDirectory
		{
			get
			{
				return new DirectoryInfo(CalculateTargetPath("__temp"));
			}
		}

		/// <summary>
		/// Before copying the source to the target, if the target exists it will
		/// be moved to this directory.
		/// </summary>
		public DirectoryInfo PreviousTargetDirectory
		{
			get
			{
				return new DirectoryInfo(CalculateTargetPath("__previous"));
			}
		}

		/// <summary>
		/// Checks whether the directories passed to the copier are valid
		/// and therefore whether the copier is in a fit state to perform the
		/// copy.
		/// </summary>
		public bool IsValid
		{
			get
			{
				if (!SourceDirectory.Exists)
				{
					return false;
				}

				if (!SourceRootDirectory.Exists)
				{
					return false;
				}

				return true;
			}
		}

		/// <summary>
		/// Whether the target directory exists and is up to date
		/// </summary>
		public bool TargetUpToDate
		{
			get
			{
				//does target exist
				if (TargetDirectory.Exists)
				{
					//target up to date
					if (TargetDirectory.LastWriteTime >= SourceDirectory.LastWriteTime)
					{
						return true;
					}
				}
				return false;
			}
		}
		#endregion

		#region Constructors
		/// <summary>
		/// 
		/// </summary>
		/// <param name="sourceDir">The directory to copy</param>
		/// <param name="sourceRootDir">The source root directory to use when calculating the target directory</param>
		/// <param name="targetRootDir">The target root directory to copy the source into</param>
		public DirectoryCopyOperation(DirectoryInfo sourceDir, DirectoryInfo sourceRootDir, DirectoryInfo targetRootDir)
		{
			SourceDirectory = sourceDir;
			SourceRootDirectory = sourceRootDir;
			TargetRootDirectory = targetRootDir;
		}
		#endregion

		#region Public interface
		/// <summary>
		/// Copies the source directory to the target location.
		/// </summary>
		/// <remarks>
		/// <para>
		///	Will only perform the copy if the target location does not exist (i.e. not previously copied)
		///	or the source is more recent than the target (i.e. has been modifed since the last copy).
		/// </para>
		/// <para>
		/// If a copy is required, will copy to a temporary location, move the existing target location
		/// (if necessary) and then move the temp location to the target location.
		/// </para>
		/// </remarks>
		public void PerformCopy()
		{
			//check if the state of the copier is ok to continue
			if (!IsValid)
			{
				throw new DirectoryNotFoundException("One or more of the directories passed to the copier do not exist.");
			}

			if (TargetUpToDate)
			{
				//no action required
				return;
			}

			//delete existing temp if exists
			if (TempTargetDirectory.Exists)
			{
				TempTargetDirectory.Delete(true);
			}

			//copy to temp location
			DirectoryCopier.CopyAll(SourceDirectory, TempTargetDirectory);

			//delete existing previous directory if exists
			if (PreviousTargetDirectory.Exists)
			{
				PreviousTargetDirectory.Delete(true);
			}
			else
			{
				//check that the parent of PreviousTargetDirectory exists
				//so existing target has somewhere to be moved to
				if (!PreviousTargetDirectory.Parent.Exists)
				{
					PreviousTargetDirectory.Parent.Create();
				}
			}

			//move existing target to previous
			if (TargetDirectory.Exists)
			{
				//creating first is causing duplicate dir exception
				//PreviousTargetDirectory.Create();
				TargetDirectory.MoveTo(PreviousTargetDirectory.FullName);
			}
			else
			{
				//the DirectoryInfo.MoveTo() method requires the parent directory
				//of the target to exist
				//bug #2 http://code.google.com/p/tjdevhouse-backuputils/issues/detail?id=2
				if (!TargetDirectory.Parent.Exists)
				{
					TargetDirectory.Parent.Create();
				}
			}

			//move the copied files from temp to target
			TempTargetDirectory.MoveTo(TargetDirectory.FullName);
		}

		/// <summary>
		/// Works out the name of the target path using the SourceDirectory
		/// and TargetRootDirectory properties
		/// </summary>
		/// <returns></returns>
		/// 
		public string CalculateTargetPath()
		{
			return CalculateTargetPath(null);
		}
		
		/// <summary>
		/// Generate the name of the target path, adding a prefix between the target root and the rest of the path.
		/// </summary>
		/// <param name="targetPrefix"></param>
		/// <returns></returns>
		public string CalculateTargetPath(string targetPrefix)
		{
			//trim trailing slashes
			string sourcePath = SourceDirectory.FullName.Trim('\\');
			string sourceRootPath = SourceRootDirectory.FullName.Trim('\\');
			string targetRootPath = TargetRootDirectory.FullName.Trim('\\');

			//cut path down to just the effected part
			string cutDownPath = sourcePath.Substring(sourceRootPath.Length, sourcePath.Length - sourceRootPath.Length).Trim('\\');

			string returnPath;
			if (targetPrefix == null)
			{
				returnPath = targetRootPath + "\\" + cutDownPath;
			}
			else
			{
				//trim any trailing slashes from the target prefix
				string trimmedPrefix = targetPrefix.Trim('\\');
				returnPath = targetRootPath + "\\" + trimmedPrefix + "\\" + cutDownPath;
			}

			return returnPath;
		}


		#endregion


	}
}
